/**
 * SequenceDao.java
 */
package com.jaws.core.compatible.dao.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.jaws.core.common.log.Log;
import com.jaws.core.compatible.dao.SequenceDao;
import com.jaws.core.compatible.model.SequencePO;
import com.jaws.core.compatible.spring.YYNamedParameterJdbcTemplate;

/**
 * @author kome
 * @date 2015年6月5日 下午2:05:18
 */
public class SequenceDaoImpl implements SequenceDao {

	private static transient Logger logger = LoggerFactory.getLogger(SequenceDaoImpl.class);

	// 这里写死,将来可以考虑注入
	// private static final String SEQUNECE_TABLE_NAME = "sys_sequence"; // sql.xml的namespace

//	@Resource(name = "namedParameterJdbcTemplate")
	protected YYNamedParameterJdbcTemplate namedParameterJdbcTemplate;

	private boolean isInitTable = false;
	
	@Resource
	private ApplicationContext context;

	/**
	 * 构造函数
	 *
	 * @param tableName
	 */
	public SequenceDaoImpl() {
	}

	@Transactional
	public void initTable(List<String> tableNames){
		Map<String, Object> params = new HashMap<>();
		for(String tableName : tableNames){
			params.put("name", tableName);
			params.put("nextValue", "1");
			namedParameterJdbcTemplate.update("insert ignore into sys_sequence (name,next_value) values(:name,:nextValue)", params);
		}
		isInitTable = true;
	}


	@PostConstruct
	public void init() {
		if(!context.containsBean("namedParameterJdbcTemplate")){
			logger.warn(Log.op("SequenceDaoImpl.init").msg("namedParameterJdbcTemplate configure not exsits,sequence init failed").toString());
			return ;
		}
		namedParameterJdbcTemplate = context.getBean("namedParameterJdbcTemplate", YYNamedParameterJdbcTemplate.class);

		Map<String, Object> param = new HashMap<>();
		namedParameterJdbcTemplate.update(
				"create table if not exists sys_sequence(name varchar(64), next_value bigint(20) default 1, primary key (name)) engine=innodb default character set=utf8 collate=utf8_general_ci;",
				param);
//		namedParameterJdbcTemplate.update("DROP PROCEDURE IF EXISTS sequence_generator;", param);
//		namedParameterJdbcTemplate.update("CREATE PROCEDURE sequence_generator(in KEYNAME VARCHAR(40), in STEP int, out NXT int) COMMENT '主键生产策略' " //
//				+ " BEGIN " //
//				// + " set autocommit=0; " //
//				+ " SET NXT = 0; " //
//				+ " SELECT next_value+1 INTO NXT FROM sys_sequence WHERE NAME = KEYNAME FOR UPDATE; " //
//				+ " IF NXT > 0 THEN " //
//				+ " UPDATE sys_sequence SET next_value = next_value + STEP WHERE NAME = KEYNAME; "//
//				+ " ELSE " //
//				+ " INSERT INTO sys_sequence (NAME, next_value) VALUES (KEYNAME, NXT) ; " //
//				+ " END IF; " //
//				// + " commit; " //
//				// + " set autocommit=1; " //
//				+ " END", param);
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
	@Override
	public long nextLong(String namespace, int step) {
		if(!isInitTable){
			return nextLongFromMysql(namespace, step);
		}else{
			return nextLongFromMysqlInitTable(namespace, step);
		}
	}

	private Long nextLongFromMysqlInitTable(final String namespace, final int step) {
		Map<String, Object> params = new HashMap<>();
		params.put("name", namespace);
		params.put("nextValue", step);

		SequencePO sequence;
		long result = -1l;
		try {
			int i = namedParameterJdbcTemplate.update("update sys_sequence set next_value=next_value+:nextValue where name=:name", params);
			if(i < 1){
				throw new RuntimeException("从数据库申请sequence号段失败!!" + namespace);
			}
			sequence = namedParameterJdbcTemplate.queryForObject("select * from sys_sequence where name=:name", params,
					new BeanPropertyRowMapper<>(SequencePO.class));
			result = sequence.getNextValue() - step;
			logger.debug("更新sequence:{} 至 {}", namespace, result);
		} catch(Exception e){
			throw new RuntimeException("从数据库申请sequence号段失败!!" + namespace);
		}

		if (result < 1) {
			throw new RuntimeException("从数据库申请sequence号段失败!!" + namespace);
		}
		return result;
	}


	private Long nextLongFromMysql(final String namespace, final int step) {
		Map<String, Object> params = new HashMap<>();
		params.put("name", namespace);
		params.put("nextValue", step);

		SequencePO sequence;
		long result = -1l;
		try {
			// lock
			namedParameterJdbcTemplate.update("lock tables sys_sequence write", params);

			int i = namedParameterJdbcTemplate.update("update sys_sequence set next_value=next_value+:nextValue where name=:name", params);
			if (i > 0) {
				sequence = namedParameterJdbcTemplate.queryForObject("select * from sys_sequence where name=:name", params,
						new BeanPropertyRowMapper<>(SequencePO.class));
			} else {
				params.put("nextValue", step + 1);
				i = namedParameterJdbcTemplate.update("insert ignore into sys_sequence (name,next_value) values(:name,:nextValue)", params);
				if (i == 0) {
					logger.warn("从数据库申请sequence号段失败:{}", namespace);
					return -1l;
				}
				sequence = new SequencePO();
				sequence.setName(namespace);
				sequence.setNextValue(step + 1);
			}
			result = sequence.getNextValue() - step;
			logger.debug("更新sequence:{} 至 {}", namespace, result);
		} finally {
			namedParameterJdbcTemplate.update("unlock tables ", params);
		}
		if (result < 1) {
			throw new RuntimeException("从数据库申请sequence号段失败!!" + namespace);
		}
		return result;
	}

	public YYNamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
		return namedParameterJdbcTemplate;
	}

}
